import { cloneDeep, isEqualWith, isFunction } from 'lodash-es';

import { createContext, useContext, useMemo, useRef } from 'react';
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

type TContext = {
    interaction: { type: any; payload: any } | null;
    dispatch: (type: any, payload: any) => void;

    env: {
        enableSearchGroupFilter: boolean;
    };
};

const creator = (initial: { envSettings?: TContext['env'] }) => {
    return create(
        immer<TContext>((set) => ({
            env: {
                enableSearchGroupFilter: false,
                ...(initial?.envSettings ?? {}),
            },
            interaction: null,
            dispatch: (type, payload) => {
                set((state) => {
                    state.interaction = { type, payload };
                });
            },
        }))
    );
};

type TUseStore = ReturnType<typeof creator>;

const Context = createContext<TUseStore | null>(null);

const StoreProvider = ({ envSettings, children }: { envSettings?: TContext['env']; children: React.ReactNode }) => {
    const ref = useRef<TUseStore | null>(null);
    if (!ref.current) {
        ref.current = creator({ envSettings });
    }

    return <Context.Provider value={ref.current}>{children}</Context.Provider>;
};

const customEqual = (value: any, other: any) => {
    if (isFunction(value) || isFunction(other)) {
        return true;
    }
    return undefined;
};

const useDeepEqual = <S, U>(selector: (state: S) => U): ((state: S) => U) => {
    const prev = useRef<U>(null);
    return (state) => {
        const next = selector(state);

        if (isFunction(next)) {
            return (prev.current = next);
        }

        if (isEqualWith(next, prev.current, customEqual)) {
            return prev.current as U;
        }

        return (prev.current = cloneDeep(next));
    };
};

type TSelector = (state: TContext) => unknown;
function useStore(): TContext;
function useStore<T extends TSelector>(selector: T): ReturnType<T>;
function useStore(selector?: TSelector) {
    const useStore = useContext(Context)!;

    if (selector) {
        return useStore(useDeepEqual(selector)) as ReturnType<TSelector>;
    } else {
        return useStore() as TContext;
    }
}

const useInteraction = <T extends (state: TContext['interaction']) => unknown>(selector: T) => {
    const interactionState = useStore((state) => {
        return selector(state.interaction) as ReturnType<T>;
    });

    const dispatchInteraction = useStore((state) => state.dispatch);

    return useMemo(() => {
        return [interactionState, dispatchInteraction] as const;
    }, [interactionState, dispatchInteraction]);
};

const useEnvSettings = () => {
    return useStore((state) => state.env);
};

export {
    //
    StoreProvider,
    useInteraction,
    useEnvSettings,
    type TContext,
};
